package com.zoe.onelink.doc.plugin.feign.provider.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.zoe.onelink.common.constant.OnelinkFeignConstant;
import com.zoe.onelink.doc.plugin.feign.model.FeignClientTemplateParam;
import com.zoe.onelink.doc.plugin.feign.provider.FeignClientTemplateParamProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spi.service.contexts.RequestMappingContext;
import springfox.documentation.spring.web.WebMvcRequestHandler;
import springfox.documentation.spring.web.plugins.CombinedRequestHandler;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>标题: </p>
 * <p>描述: </p>
 * <p>版权: Copyright (c) 2022</p>
 * <p>公司: 智业软件股份有限公司</p>
 *
 * @version: 1.0
 * @author: ruansheng
 * @date: 2022-03-31
 */
public class DefaultFeignClientParamProviderImpl implements FeignClientTemplateParamProvider {

    @Value("${spring.application.name:}")
    private String serviceName;

    /**
     * k  = serviceName
     * t1 = OnelinkFeignConstant.SERVICE
     * t2 = OnelinkFeignConstant.URL
     */
    private Map<String, Tuple> serviceConstantMap;

    public DefaultFeignClientParamProviderImpl() {
        this.initServiceConstants();

    }

    private void initServiceConstants() {
        try {
            Map<String, String> serviceMap = Arrays.stream(OnelinkFeignConstant.Service.class.getDeclaredFields())
                    .collect(Collectors.toMap(Field::getName, v -> ReflectUtil.getStaticFieldValue(v).toString()));
            this.serviceConstantMap = serviceMap.entrySet().stream().collect(Collectors.toMap(
                    Map.Entry::getValue,
                    e -> new Tuple("OnelinkFeignConstant.Service." + e.getKey(), "OnelinkFeignConstant.Url." + e.getKey())
            ));
        } catch (Exception e) {
            this.serviceConstantMap = Collections.emptyMap();
        }
    }

    @Override
    public FeignClientTemplateParam generateParam(OperationContext context) {
        RequestMappingContext requestMappingContext = (RequestMappingContext) ReflectUtil.getFieldValue(context, "requestContext");
        String returnType = context.getReturnType().toString().replaceAll("[a-zA-Z]+\\.", "").replace("ResultVO", "ApiResult");
        if (!returnType.startsWith("ApiResult")) {
            returnType = String.format("ApiResult<%s>", returnType);
        }
        String methodName = context.getName();
        List<String> args = context.getParameters().stream()
                .map(resolvedMethodParameter -> {
                    StringBuilder argSb = new StringBuilder();
                    String anno = resolvedMethodParameter.getAnnotations().stream()
                            .map(annotation -> annotation.annotationType().getSimpleName())
                            .reduce((s1, s2) -> s1 + " @" + s2)
                            .orElse("");
                    if (StrUtil.isNotBlank(anno)) {
                        argSb.append("@").append(anno).append(" ");
                    }
                    String typeName = resolvedMethodParameter.getParameterType().getErasedType().getSimpleName();
                    String varName = resolvedMethodParameter.defaultName().orElse("var");
                    return argSb.append(typeName).append(" ").append(varName).toString();
                }).collect(Collectors.toList());
        String argStr = StrUtil.join(", ", args);
        Object handler = ReflectUtil.getFieldValue(requestMappingContext, "handler");
        HandlerMethod handlerMethod = handler instanceof CombinedRequestHandler
                ? ((CombinedRequestHandler) handler).getHandlerMethod()
                : ((WebMvcRequestHandler) handler).getHandlerMethod();
        Method controllerMethod = handlerMethod.getMethod();
        Annotation anno = Arrays.stream(controllerMethod.getDeclaredAnnotations())
                .filter(annotation -> annotation.annotationType().isAnnotationPresent(RequestMapping.class))
                .findFirst()
                .orElse(null);
        String reqMethodName = anno == null ? "PostMapping" : anno.annotationType().getSimpleName();
        String reqPath = anno == null ? "" : ArrayUtil.get(AnnotationUtils.getValue(anno), 0);
        String controllerName = handlerMethod.getBeanType().getSimpleName();
        String clientName = controllerName.endsWith("Controller") ? controllerName.replace("Controller", "Client") : controllerName + "Client";
        RequestMapping requestMappingDeclaredAnnotation = handlerMethod.getBeanType().getDeclaredAnnotation(RequestMapping.class);
        String[] basePathArr = requestMappingDeclaredAnnotation == null ? new String[0] : requestMappingDeclaredAnnotation.value();
        String basePath = basePathArr.length == 0 ? "" : basePathArr[0];
        Tuple clientTuple = this.serviceConstantMap.get(serviceName);
        String clientServiceName, clientUrl;
        if (clientTuple == null) {
            clientServiceName = String.format("\"%s\"", this.serviceName);
            clientUrl = String.format("\"${onelink.services.endpoint.%s:}\"", this.serviceName);
        } else {
            clientServiceName = clientTuple.get(0);
            clientUrl = clientTuple.get(1);
        }
        return new FeignClientTemplateParam()
                .setArgs(argStr)
                .setPath(basePath)
                .setClientServiceName(clientServiceName)
                .setClientUrl(clientUrl)
                .setClientName(clientName)
                .setRequestMapping(reqMethodName)
                .setMappingUrl(reqPath)
                .setResultType(returnType)
                .setMethodName(methodName)
                .setCreatedDate(DateUtil.now());

    }

}
